package experiment5.demo2;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;

/**
 * @author Anonymous
 * @description
 * @date 2020/3/9 11:12
 * <p>
 * 服务器需要转发数据
 * 1. 数据转发
 * 群聊
 * 私聊
 * 2. 用户注册
 * 用户 ==> class
 * <p>
 * 私聊，群聊属于用户功能
 * 用户这里看做是一个类
 * 成员变量
 * 输入流
 * 输出流
 * 用户ID
 * 用户名
 * 连接状态标记
 * 成员方法:
 * 接收方法
 * 利用客户端连接服务器对应的Socket得到输入流接收用户发送的数据
 * 发送方法
 * 群聊
 * 遍历整个有效用户
 * 私聊
 * 找到对应用户
 * 利用客户端连接服务器对应的Socket得到输出流发送数据
 * 【成员内部类】
 * 用户做出一个成员内部类
 * 作为Server服务器类的一个成员变量内部类
 * <p>
 * 用户注册流程
 * 1. ServerSocket Accept客户端连接，获取对应Socket对象
 * 2. 记录在线人数，创建一个新的UserSocket
 * 3. Map中映射对应的UserSocket，Key 为ID， Value是UserSocket
 */
public class Server {

    /**
     * 用户ID和UserSocket映射
     */
    private HashMap<Integer, UserSocket> userMap;

    /**
     * 累加访客人数
     */
    private static int count = 0;

    /**
     * Server构造方法，用于初始化底层保存数据的HashMap双边队列
     */
    public Server() {
        userMap = new HashMap<>();
    }

    /**
     * 服务器启动方法
     *
     * @throws IOException IO异常
     */
    public void start() throws IOException {
        // 启动服务器，同时监听8888端口
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务器启动");

        // 服务器始终处于一个保存连接的状态
        while (true) {
            // 接收客户端请求，得到一个Socket对象
            Socket socket = serverSocket.accept();

            // 创建UserSocket用于注册，并且保存到userMap当中
            count += 1;
            UserSocket userSocket = new UserSocket(count, socket);
            userMap.put(count, userSocket);

            // 启动当前UserSocket服务
            new Thread(userSocket).start();
        }
    }

    /**
     * @author Anonymous
     * @description 用户Socket类，需要完成绑定操作，并且是一个线程类
     * @date 2020/3/9 11:23
     */
    class UserSocket implements Runnable {

        /*
         * 对应当前客户端连接服务器对应Socket生成输入流和输出流
         */
        private DataInputStream inputStream;
        private DataOutputStream outputStream;

        /**
         * 用户ID号，是当前用户的唯一表示，不可以重复
         */
        private int userId;

        /**
         * 用户名，用于注册，同时在发送数据时给予其他用户标记
         */
        private String userName;

        /**
         * 是否连接状态标记
         */
        private boolean connetion;

        /**
         * 创建UserSocket对象，需要的参数使用userID，和对应的Socket对象
         *
         * @param userId 当前用户的ID号
         * @param socket 客户端连接服务器对应的Socket
         */
        public UserSocket(int userId, Socket socket) {
            this.userId = userId;

            try {
                inputStream = new DataInputStream(socket.getInputStream());
                outputStream = new DataOutputStream(socket.getOutputStream());
                connetion = true;
            } catch (IOException e) {
                e.printStackTrace();
                connetion = false;
            }

            try {
                // 用户在创建Send线程时，首先会将用户的名字发送给服务器
                assert inputStream != null;
                this.userName = inputStream.readUTF();

                // 广播告知所有人，ID:XXX 姓名: XXX 上线 【群聊，系统广播】
                sendOther("ID:" + this.userId + " " + this.userName + "来到直播间", true);
                // 服务器告诉当前客户端，你已经进入聊天室
                send("欢迎来到聊天室");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 接收客户端发送的数据，用于转发操作
         *
         * @return 用户发送的数据
         */
        public String receive() {
            String msg = null;

            try {
                // 接收用户发送到服务器需要服务器转发的数据
                msg = inputStream.readUTF();
            } catch (IOException e) {
                e.printStackTrace();
                /*
                发生异常：
                    1. 连接状态修改
                    2. 关闭资源
                    3. 删除在userMap中对应的数据【留下】

                    对号是书签 快捷键 F11
                    查看当前项目所有的书签 ALT + 2
                 */
                connetion = false;
                CloseUtil.closeAll(inputStream, outputStream);
            }

            return msg;
        }

        /**
         * 发送数据到客户端
         *
         * @param msg 需要发送的数据
         */
        public void send(String msg) {
            try {
                outputStream.writeUTF(msg);
                outputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
                /*
                发生异常：
                    1. 连接状态修改
                    2. 关闭资源
                    3. 删除在userMap中对应的数据
                 */
                connetion = false;
                CloseUtil.closeAll(inputStream, outputStream);
            }
        }

        /*
        这里需要一个方法
            1. 私聊判断
            2. 群聊
        这里需要根据msg进行判断
            1. 如果是@数字: 前缀
                私聊
                    通过HashMap --> ID --> UserSocket --> 转发消息
            2. 非@数字:开头群聊
                a. 系统播报
                b. 私人发送
                这里需要做一个标记
                    获取所有在线用户，判断除自己之外，其他人转发消息
         */

        /**
         * 转发数据判断方法，msg需要处理，选择对应的私聊和群发，同时要判断是否是系统发送消息
         *
         * @param msg 需要转发的消息
         * @param sys 系统标记
         */
        public void sendOther(String msg, boolean sys) {
            if (msg.startsWith("@") && msg.contains(":")) {
                // 私聊
                // @1:XXXXXX
                Integer id = Integer.parseInt(msg.substring(1, msg.indexOf(":")));
                String newMsg = msg.substring(msg.indexOf(":"));

                UserSocket userSocket = userMap.get(id);
                // 如果没有对应的UserSocket用户存在，无法发送消息
                if (userSocket != null) {
                    // ID:1 小磊磊悄悄的对你说:XXXX
                    userSocket.send("ID:" + this.userId + " " + this.userName + "悄悄的对你说" + msg);
                }

            } else {
                // 群聊
                // 从userMap中获取对应的所有value，也就是所有UserSocket对象Collection集合
                Collection<UserSocket> values = userMap.values();
                for (UserSocket userSocket : values) {
                    // 不需要将消息发送给自己
                    if (userSocket != this) {
                        // 判断是不是系统消息
                        if (sys) {
                            userSocket.send("系统公告:" + msg);
                        } else {
                            userSocket.send("ID:" + this.userId + " " + this.userName + msg);
                        }
                    }
                }
            }
        }

        /**
         * 线程代码
         */
        @Override
        public void run() {
            while (connetion) {
                // 使用receive收到的消息作为参数，同时标记非系统消息，调用sendOther
                sendOther(receive(), false);
            }
        }
    }

    public static void main(String[] args) {
        // 启动服务器！！！
        Server server = new Server();
        try {
            server.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
